package com.powernode.ssm.interceptor;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import com.sun.org.apache.bcel.internal.generic.NEW;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ✅ 增强版 MyBatis SQL 打印拦截器（调试用，仅 dev/test 环境生效）
 *
 * 核心功能：
 * - [x] 打印原始 SQL（带 ? 占位符）
 * - [x] 打印参数对象、参数名及参数映射数量
 * - [x] 模拟打印真实 SQL（将 ? 替换为参数值，⚠️仅供调试查看，非真实执行语句）
 * - [x] 格式化 SQL（关键字换行，提升可读性）
 * - [x] 打印 SQL 执行时间与慢查询警告（可配置阈值，默认 1000ms）
 * - [x] 针对 MyBatis-Plus 分页插件 LIMIT ?,? 自动替换为真实 LIMIT offset, limit
 * - [x] 支持集合参数，如 IN (?) 中的集合自动展开为多个值
 * - [x] 非生产环境生效：通过 @Profile("!prod") 限制
 *
 * 🔒 注意事项：
 * - 本拦截器打印的 "真实 SQL" 是通过手动替换 ? 实现，仅供调试参考，
 *   顺序依赖 BoundSql ParameterMapping，不可用于生产或实际 SQL 操作！
 * - 若参数顺序异常、复杂嵌套对象等场景，可能 SQL 模拟不准确，请以实际执行为准。
 */
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
})
@Slf4j
@Component
//@Profile("!prod") // 仅在非 prod 环境（如 dev/test）生效
@ConditionalOnProperty(prefix = "myLog.sqlPrint", name = "enable", havingValue = "true")
public class SqlPrintInterceptor implements Interceptor {

    private static final String DEFAULT_DATE_FORMAT = DatePattern.NORM_DATETIME_PATTERN; // yyyy-MM-dd HH:mm:ss
    private String dateFormatPattern = DEFAULT_DATE_FORMAT;
    private SimpleDateFormat dateFormat;

    // 慢查询阈值，默认 1000ms
    private long slowQueryThresholdMillis = 1000;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();

        // ------ 1. 原始信息打印 ------
        String originalSql = boundSql.getSql() != null ? boundSql.getSql().trim() : "";
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        log.info("[SQL] 📥 原始SQL: \n{}", originalSql);
        log.info("[SQL] 📦 参数对象类型: {}", parameterObject != null ? parameterObject.getClass().getName() : "null");
        if (parameterMappings != null && !parameterMappings.isEmpty()) {
            log.info("[SQL] 📋 参数映射数量: {}", parameterMappings.size());
            for (ParameterMapping mapping : parameterMappings) {
                log.info("[SQL]     参数名: {}", mapping.getProperty());
            }
        }

        // ------ 2. 构建模拟真实 SQL（手动替换 ?，仅调试用） ------
        String realSql = buildRealSql(originalSql, parameterObject, parameterMappings);

        // ------ 3. 格式化 SQL，提升可读性 ------
        String prettySql = formatSql(realSql);

        // ------ 4. 打印模拟真实 SQL 与格式化后 SQL ------
        log.info("[SQL] ✅ 模拟真实SQL（仅供调试，非真实执行语句）:\n{}", realSql);
        log.info("[SQL] 🎨 格式化后 SQL:\n{}", prettySql);

        // ------ 5. 执行 SQL 并计算耗时 / 慢查询检测 ------
        long startTime = System.nanoTime();
        try {
            return invocation.proceed();
        } finally {
            long endTime = System.nanoTime();
            long durationMs = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);

            if (durationMs > slowQueryThresholdMillis) {
                log.warn("[SQL] ⚠️ 慢查询预警 | 耗时: {} ms (超过阈值 {} ms) | SQL: \n{}", durationMs, slowQueryThresholdMillis, realSql);
            } else {
                log.info("[SQL] ⏱️ SQL 执行耗时: {} ms | SQL: {}", durationMs, realSql);
            }
        }
    }

    /**
     * 构建模拟真实 SQL（将 ? 替换为参数值，仅用于日志打印, 非真实执行）
     * ⚠️ 注意：参数顺序必须与 SQL 中 ? 顺序严格一致，否则可能模拟错误！
     */
    private String buildRealSql(String sql, Object parameterObject, List<ParameterMapping> parameterMappings) {
        if (sql == null || sql.isEmpty()) {
            return "";
        }

        String result = sql;

        // === 【1】MyBatis-Plus 分页插件特例处理：自动替换 LIMIT ?,? 为真实 LIMIT offset, limit ===
        if (sql.trim().toUpperCase().endsWith("LIMIT ?,?")) {
            if (parameterObject instanceof Map) {
                Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
                Object pageObj = paramMap.get("page");

                if (pageObj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
                    com.baomidou.mybatisplus.extension.plugins.pagination.Page<?> page =
                            (com.baomidou.mybatisplus.extension.plugins.pagination.Page<?>) pageObj;

                    int current = (int) page.getCurrent();
                    int size = (int) page.getSize();
                    int offset = (current - 1) * size;
                    int limit = size;

                    String replaced = sql.replace("LIMIT ?,?", "LIMIT " + offset + ", " + limit);
                    log.info("[SQL] 🎯 检测到 MyBatis-Plus 分页 LIMIT ?,?，已替换为 LIMIT {}, {}", offset, limit);
                    return replaced;
                } else {
                    log.debug("[SQL] 🧩 未识别到 Page 类型分页参数，类型 = {}", pageObj != null ? pageObj.getClass() : "null");
                }
            }
        }

        // === 【2】常规参数替换逻辑：将 ? 依次替换为参数值 ===
        if (parameterMappings != null && !parameterMappings.isEmpty()) {
            for (ParameterMapping mapping : parameterMappings) {
                String propertyName = mapping.getProperty();
                if (StrUtil.isBlank(propertyName)) {
                    continue;
                }

                // 忽略 MyBatis-Plus 内部参数（如 param1, param2, _parameter）
                if (isLikelyMyBatisPlusInternalParam(propertyName)) {
                    log.debug("[SQL] ⚠️ 忽略疑似 MyBatis-Plus 内部参数: {}", propertyName);
                    continue;
                }

                Object value = getFieldValueSafely(parameterObject, propertyName);
                result = replacePlaceholder(result, value);
            }
        }

        return result;
    }

    /**
     * 判断是否为 MyBatis-Plus 内部常用参数名（如 param1, param2, _parameter, plus...）
     */
    private boolean isLikelyMyBatisPlusInternalParam(String propertyName) {
        if (propertyName == null) return false;
        return propertyName.contains("plus") ||
                propertyName.contains("param") ||
                propertyName.contains("first") ||
                propertyName.contains("second") ||
                propertyName.matches("^[0-9]+$"); // 如 param1, param2
    }

    /**
     * 安全地从参数对象中获取字段值（支持 Map / 嵌套字段 / JavaBean）
     */
    private Object getFieldValueSafely(Object parameterObject, String fieldName) {
        if (parameterObject == null || StrUtil.isBlank(fieldName)) {
            return null;
        }

        try {
            // --- 情况1：参数是 Map ---
            if (parameterObject instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) parameterObject;
                // 支持嵌套字段，如 "user.name"
                if (fieldName.contains(".")) {
                    String[] parts = fieldName.split("\\.", 2);
                    if (parts.length == 2) {
                        Object nested = map.get(parts[0]);
                        return nested != null ? getFieldValueSafely(nested, parts[1]) : null;
                    }
                }
                // 直接取 key
                return map.get(fieldName);
            }

            // --- 情况2：普通 Java 对象，尝试通过反射获取字段值 ---
            return getFieldValueFromObject(parameterObject, fieldName);

        } catch (Exception e) {
            log.warn("[SQL] ⚠️ 无法获取字段值 [{}]，对象类型：{}，原因：{}", fieldName,
                    parameterObject != null ? parameterObject.getClass().getName() : "null", e.getMessage());
            return null;
        }
    }

    /**
     * 反射获取对象字段值（通过 getter，如 getUserId -> getUserId()）
     */
    private Object getFieldValueFromObject(Object obj, String fieldName) throws Exception {
        String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        Method method = obj.getClass().getMethod(getterName);
        return method.invoke(obj);
    }

    /**
     * 替换 SQL 中的下一个 ? 为实际值，支持 null、String、Date、数字等类型
     */
    private String replacePlaceholder(String sql, Object value) {
        if (value == null) {
            return sql.replaceFirst("\\?", "NULL");
        } else if (value instanceof Number || value instanceof Boolean) {
            return sql.replaceFirst("\\?", value.toString());
        } else if (value instanceof java.util.Date) {
            return sql.replaceFirst("\\?", "'" + dateFormat.format((java.util.Date) value) + "'");
        } else if (value instanceof Collection) {
            return replacePlaceholdersForCollection(sql, (Collection<?>) value);
        } else if (value.getClass().isArray()) {
            return replacePlaceholdersForCollection(sql, Arrays.asList((Object[]) value));
        } else {
            // 字符串类型需转义单引号并包裹
            return sql.replaceFirst("\\?", "'" + escapeSql(value.toString()) + "'");
        }
    }

    /**
     * 替换集合类型的参数（如 IN (?) 中的多个值）
     */
    private String replacePlaceholdersForCollection(String sql, Collection<?> collection) {
        String result = sql;
        for (Object item : collection) {
            result = replacePlaceholder(result, item);
        }
        return result;
    }

    /**
     * 简单 SQL 关键字格式化（美化排版）
     */
    private String formatSql(String sql) {
        return sql
                .replaceAll("(?i)select", "\nSELECT")
                .replaceAll("(?i)from", "\nFROM")
                .replaceAll("(?i)where", "\nWHERE")
                .replaceAll("(?i)and", "\n  AND")
                .replaceAll("(?i)or", "\n  OR")
                .replaceAll("(?i)order\\s+by", "\nORDER BY")
                .replaceAll("(?i)group\\s+by", "\nGROUP BY")
                .replaceAll("\\s+", " ") // 去掉多余空格
                .trim();
    }

    /**
     * 转义 SQL 字符串中的单引号（防止单引号破坏语句）
     */
    private String escapeSql(String input) {
        return input.replace("'", "''");
    }

    // ========== 插件生命周期方法 ==========

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 读取慢查询阈值配置
        String threshold = properties.getProperty("slowQueryThresholdMillis");
        if (StrUtil.isNotBlank(threshold)) {
            this.slowQueryThresholdMillis = Long.parseLong(threshold);
            log.info("[SQL] ⚙️ 慢查询阈值设置为 {} ms", this.slowQueryThresholdMillis);
        }

        // 读取日期格式配置（可选）
        String dateFmt = properties.getProperty("dateFormat");
        if (StrUtil.isNotBlank(dateFmt)) {
            this.dateFormatPattern = dateFmt;
        }
        this.dateFormat = new SimpleDateFormat(this.dateFormatPattern);
    }
}